查看原文
其他

Taro2.x 跨端开发实践

祝求智 58技术 2022-03-15

导读

随着业务的快速发展以及要拓展在线渠道至APP、小程序以及M站。与此同时,用户数量的上升需要优化前端的性能和用户体验。本文主要介绍业主业务如何利用Taro2.x实现跨端来进行业务的快速迭代。


背景

业主业务之前一直是以h5的形式嵌入安居客和58app中的,用户如果需要自己发布卖房信息或者委托经纪人,是只能在app中操作的。
为了达到以下三个目标:

  1. 提高用户体验

  2. 需要将发房渠道扩展到小程序和M站

  3. 节约成本和减少重复性劳动

我们尝试进行跨端开发(React Native、小程序和H5)。

对市面上主流小程序框架来进行了对比:

  1. 支持React Native、小程序和H5的只有Taro和uni-app

  2. 安居客小程序是以Taro开发的,提供了宿主环境给到各业务方使用,以及公司内部有MPS CLI(WuBa MiniProgram for Standardization,小程序全端跨平台开发解决方案)

考虑到时间成本以及技术体系,最终我们选定Taro来进行尝试多端开发。

Taro[2.x]的官方文档是这样写的:

使用 Taro,我们可以只书写一套代码,再通过 Taro 的编译工具,将源代码分别编译出可以在不同端(微信 / 京东 / 百度 / 支付宝 / 字节跳动 小程序、快应用、H5、React-Native 等)运行的代码。

看完文档以为跨端开发是这样的:

但,实际上是这样的:

虽然官方说了支持RN,但是对应的各端开发前注意-React Native是最多的,显然利用Taro编译Rn,需要的前置条件相比小程序和H5来说是比较多的。

我们在实践过程中,遇到了以下的问题:

  1. 官方提供的RN 0.59.9版本与安居客和58app不兼容

  2. taro有些api在Rn端是依赖原生模块的,而这些模块在安居客58app没有集成

  3. 样式问题,RN对css的样式支持度很低

  4. jsx语法限制,由于小程序的限制,有些写法可能在rn和h5端没有问题,但是小程序端会报错

  5. RN端能力不足

  6. Taro官方组件库TaroUI不支持RN

在解决问题之前,我们大概了解下Taro是如何将一套代码编译成可运行在多端的


Taro框架原理

Taro是一个编译时框架,采用的是编译原理的思想,是对输入的源代码进行语法分析,语法树构建,随后对语法树进行转换操作再解析生成目标代码的过程。

Taro1.x编译构建系统是taro自研的:

在2.x时,则是将各端编译器剥离,CLI只做区分编译平台、处理不同平台编译入参等操作,随后再调用对应平台的 runner 编译器 做代码编译操作,而原来大量的 AST 语法操作改造成 Webpack Plugin 以及 Loader,交给 Webpack 来处理。

Taro2.x:

解决问题

1. 官方提供的RN 0.59.9版本,安居客和58app不支持

这个问题只能修改Taro自身的RN版本,修改成安居客和58app支持的对应版本

2. taro有些api在Rn端是依赖原生模块的,而这些模块在安居客58app没有集成

RN开发时,如果你用了依赖原生模块的方法,而这个模块原生没有集成,那么就会出现红屏警告。

针对上面两个问题我们选择了私有化Taro Cli来进行解决:

我们是基于Taro2.2.6版本开发的,为了使Taro兼容58RN工程我们对源码做了以下改造:

  1. @tarojs/cli修改react-native版本

  2. @tarojs/helper、@tarojs/taro-rn、@tarojs/rn-runner修改cli依赖,删除安居客、58不支持的原生依赖(expo)

修改后上传到了58npm,然后在项目的.npmrc中添加对应的registry

上面两个问题解决后,就可以进行RN的开发了:

整个 RN 端的开发流程如下:

执行yarn dev:rn,Taro会先编译成RN代码放在rn_temp下,再通过metro server启动服务打包rn_temp下的js文件生成jsbundle,就可以在app通过127.0.0.1:8081访问rn页面。

那么这里来解决第三个问题:3. 样式问题,RN对css的样式支持度很低

这是官方的开发注意说明:

样式上 H5 最为灵活,小程序次之,RN 最弱,统一多端样式即是对齐短板,也就是要以 RN 的约束来管理样式,同时兼顾小程序的限制,核心可以用三点来概括:

  • 使用 Flex 布局

  • 基于 BEM 写样式

  • 采用 style 属性覆盖组件样式

RN 中 View 标签默认主轴方向是 column,如果不将其他端改成与 RN 一致,就需要在所有用到 display: flex 的地方都显式声明主轴方向。

那么这里怎么将RN的样式区分出来呢?

Taro是有提供样式的条件编译:

但实际使用时,2.2.6 样式文件条件编译*.rn.scss不起作用,文件内条件编译是可以的,但是编译时会有警告。

因为2.x里用的都是webpack,这里直接用webpack.NormalModuleReplacementPlugin来进行修改,将RN的样式剥离出来,这样编写样式时就能以RN为基准来进行编写。

webpackChain(chain, webpack) { chain.merge({ plugin: { NormalModuleReplacementPlugin: { plugin: new webpack.NormalModuleReplacementPlugin( /\.scss/, function (resource) { let isReplace = true; for (let i = 0; i < BUILD_TYPE.length; i++) { const ext = BUILD_TYPE[i]; if (resource.request.indexOf(`.${ext}.scss`) > -1) { isReplace = false; break; } } const replaceStr = resource.request.replace( /\.scss/, `.rn.scss` ); if ( fs.existsSync(resource.context + '/' + replaceStr) && isReplace ) { resource.request = replaceStr; } } ), }, }, }); },

在index.scss中直接@import ./index.rn.scss,然后补充小程序和h5支持的如fixed等css属性。

还有一个问题是,Taro处理样式文件时,每个样式文件都会有这样一个函数:

// 一般app 只有竖屏模式,所以可以只获取一次 widthconst deviceWidthDp = Dimensions.get('window').widthconst uiWidthPx = 375
function scalePx2dp (uiElementPx) { return uiElementPx * deviceWidthDp / uiWidthPx }}

然后将10px替换成scalePx2dp(10),这样就会发现在iPhone 6 和6Plus上的字体大小是不一致的,会根据屏幕来进行等比缩放的

但在业主项目中,UI规范是要求字体大小、宽高不根据屏幕进行缩放,h5不要使用rem,所以需要通过修改postcss-pxtransform、rn-runner 和Taro.pxTransform

怎么样修改node_modules中的代码呢,一般是私有化,但也有很方便的方法。

我们可以直接使用patch-package修改node_modules中包的源码

然后在package.json中添加:

"scripts": {+ "postinstall": "patch-package" }




其中在rn中是将className处理成style的,覆盖组件样式可以通过style传递,但style可能是数组,这点需要注意下。

4. jsx语法限制,由于小程序的限制,有些写法可能在rn和h5端没有问题,但是小程序端会报错

由于小程序的限制,有些写法可能在rn和h5端没有问题,但是小程序端会报错,所以使用Taro编写代码时需要遵循Taro的最佳实践

5. RN端能力不足

6. Taro官方组件库TaroUI不支持RN

Taro提供的组件和Api相对比较基础,通用性更强,但基本都是以小程序提供的api为基准,适配到h5和rn,在rn端是利用Expo进行支持的,但是58和安居客app端并没有集成对应的依赖,所以需要利用app端已有的功能进行封装,抹平业务中的平台判断。

例如rn中的请求Request是必需的API,利用原生Module提供的Request进行支持:

新增rn-request.ts:

import { NativeModules } from 'react-native';
declare type RequestData = { method: string; url: string; params: any;};
export function fetch(requestData: RequestData): Promise<any> | null { return new Promise<any>((resolve, reject) => NativeModules.HTTPModule.Request( requestData, (response: any) => { resolve(response); }, (error: string) => { reject(error); }));}

在fetch.ts中:

export default async function fetch(options: RequestParams) { const { url, data = {}, method = 'GET', header = {}, credentials = 'include' } = options; try { let result; if (process.env.TARO_ENV === 'rn') { result = await require('./rn-request').fetch({ url, method, params: data || {}, }); } else { result = await Taro.request({ url, method, data, header, credentials, }); } ... // do somethine ...

业务中使用到的业务组件如picker、modal等,不同端的Api如登录、跳转等需要我们自行封装。

使用条件编译来抹平不同端的差异:

if (process.env.TARO_ENV === 'weapp') { // 微信小程序端执行逻辑} else if (process.env.TARO_ENV === 'h5') { // h5 端执行逻辑} else if (process.env.TARO_ENV === 'rn') { // react-native 端执行逻辑}

发布:

Rn是利用58的rn热更新平台进行打包,所以需要将rn_temp下的文件推送到特定分支,比如rn_deploy,然后在热更新平台配置仓库地址、分支,进行构建ios和安卓包。


小程序则是作为分包接入安居客微信小程序中,利用58的小程序全端跨平台开发解决方案MPS CLI发布。


H5的还是走云平台部署流程。

最终实现的效果:


总结与展望

我们利用Taro开发了新版房贷计算器、业主频道、业主直卖表单和管理详情页,以及正在开发的业主房源管理页等。
达到了我们的目标:

  1. 提高用户体验

  2. 需要将发房渠道扩展到小程序和M站

  3. 节约成本和减少重复性劳动

但在实践中,踩了不少坑,在开发业务的同时需要针对api和组件进行底层封装、Taro框架调整,以及样式适配ios、安卓、小程序和h5,实际业务开发时间比开发H5相同需求多了1倍左右。

目前跨端开发还处于摸石头过河的阶段,多端统一的组件、sdk积累较少,在组件库、sdk丰富完善后,可以抹平各端差异,开发效率基本接近目前小程序、h5开发情况。

目前在和app同学一起补全RN的端能力,推动app、小程序的底层api和组件的统一,实现业务开发无需关注各端差异,专注于业务本身,高效开发。理想情况下,使用Taro编写的业务代码可以无缝对接到多端。



作者简介:


祝求智:房产事业群--用户前端开发部,前端开发工程师

参考文献:
  1. https://taro-docs.jd.com/taro/docs/2.x/README

  2. https://aotu.io/notes/2020/01/08/taro-2-0/

  3. https://aotu.io/notes/2018/06/07/Taro/

福利环节

 为了鼓励优质内容传播,【58技术】公众号近期会持续推出不定期活动奖励。

  1. 评论区互动留言,即可参与此次活动

  2. 留言转发集赞,点赞量前三名(点赞数需大于10)可获得定制版新年代码台历一本

  3. 活动时间:截至2021年1月15日





您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存